﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Linq;
using SGCG.Model;
using SGCG.Helpers;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace SGCG.Actions
{


    public class SelectAction : Action
    {

        public abstract class SubAction : IGenericAction
        {
            public SubAction(SelectAction selectAction)
            {
                this.selectAction = selectAction;
            }
            private SelectAction selectAction;
            public SelectAction SelectAction
            {
                get { return this.selectAction; }
            }
            public abstract void Initialize();
            public abstract void HandleClick(MouseButtonEventArgs eventArgs, UIElement plotArea);
            public abstract void HandleMouseMove(MouseEventArgs eventArgs, UIElement plotArea);
            public abstract void Cancel();
        }

        public class SelectSubAction : SubAction
        {
            public SelectSubAction(SelectAction selectAction) : base(selectAction) { }

            public override void Initialize()
            {
                SelectAction.ClearSelection();
            }

            public override void HandleClick(MouseButtonEventArgs eventArgs, UIElement plotArea)
            {
                GraphicObject graphicObject = SelectAction.GetGraphicObjectAtCoordinates(eventArgs.GetPosition(plotArea));

                if (graphicObject != null)
                {
                    if (this.SelectAction.Selection.Contains(graphicObject))
                    {
                        this.SelectAction.Selection.Remove(graphicObject);
                    }
                    else
                    {
                        this.SelectAction.Selection.Add(graphicObject);
                    }
                }
            }

            public override void HandleMouseMove(MouseEventArgs eventArgs, UIElement plotArea)
            {
                if (SelectAction.GetGraphicObjectAtCoordinates(eventArgs.GetPosition(plotArea)) != null)
                {
                    ((FrameworkElement)plotArea).Cursor = Cursors.Hand;
                }
                else
                {
                    ((FrameworkElement)plotArea).Cursor = Cursors.Arrow;
                }
            }

            public override void Cancel()
            { }
        }

        public class MoveSubAction : SubAction
        {
            public MoveSubAction(SelectAction selectAction) : base(selectAction) { }

            public override void Initialize()
            { }

            private bool Moving { get; set; }
            private Point BaseLocation { get; set; }
            private Model.TranslateTransform CurrentTransform { get; set; }

            public override void HandleClick(MouseButtonEventArgs eventArgs, UIElement plotArea)
            {
                if (Moving)
                {
                    Moving = false;
                    using (new BatchRedrawOperation(SelectAction.PellBitmap))
                    {
                        this.SelectAction.Selection.Each(g =>
                            {
                                g.CurrentTransforms.Remove(CurrentTransform);
                                g.ConsolidateTransform(CurrentTransform);
                            });
                    }
                    CurrentTransform = null;
                }
                else
                {
                    Moving = true;
                    CurrentTransform = new Model.TranslateTransform();
                    using (new BatchRedrawOperation(SelectAction.PellBitmap))
                    {
                        this.SelectAction.Selection.Each(g => g.CurrentTransforms.Add(CurrentTransform));
                    }
                    BaseLocation = eventArgs.GetPosition(plotArea);
                }
            }

            public override void HandleMouseMove(MouseEventArgs eventArgs, UIElement plotArea)
            {
                if (Moving)
                {
                    using (new BatchRedrawOperation(SelectAction.PellBitmap))
                    {
                        CurrentTransform.Offset = eventArgs.GetPosition(plotArea).Subtract(BaseLocation);
                    }
                }
            }

            public override void Cancel()
            {
                using (new BatchRedrawOperation(SelectAction.PellBitmap))
                {
                    SelectAction.Selection.Each(g => g.CurrentTransforms.Remove(CurrentTransform));
                }
            }

        }

        public class RotateSubAction : SubAction
        {
            public RotateSubAction(SelectAction selectAction) : base(selectAction) { }

            public override void Initialize()
            {
 	            UserMessage.Status("Aguardando pivô de rotação...");
            }

            private enum ClickStateEnum
            {
                REST,
                WAITING_FOR_SECOND_POINT,
                ROTATING,
            }

            private ClickStateEnum ClickState { get; set; }
            private Point BasePoint { get; set; }
            private Point SecondPoint { get; set; }
            private Model.RotateTransform CurrentTransform { get; set; }

            public override void HandleClick(MouseButtonEventArgs eventArgs, UIElement plotArea)
            {
                switch (ClickState)
                {
                    case ClickStateEnum.REST:
                        BasePoint = eventArgs.GetPosition(plotArea);
                        ClickState = ClickStateEnum.WAITING_FOR_SECOND_POINT;
                        UserMessage.Status("Aguardando primeiro ponto de ângulo...");
                        break;

                    case ClickStateEnum.WAITING_FOR_SECOND_POINT:
                        SecondPoint = eventArgs.GetPosition(plotArea);
                        CurrentTransform = new Model.RotateTransform()
                        {
                            RelativeTo = BasePoint,
                            Angle = 0
                        };
                        using (new BatchRedrawOperation(SelectAction.PellBitmap))
                        {
                            SelectAction.Selection.Each(g => g.CurrentTransforms.Add(CurrentTransform));
                        }
                        ClickState = ClickStateEnum.ROTATING;
                        UserMessage.Status("Movimente o cursor para rotacionar os objetos.");
                        break;

                    case ClickStateEnum.ROTATING:
                        using (new BatchRedrawOperation(SelectAction.PellBitmap))
                        {
                            SelectAction.Selection.Each(g =>
                                {
                                    g.CurrentTransforms.Remove(CurrentTransform);
                                    g.ConsolidateTransform(CurrentTransform);
                                });
                        }
                        ClickState = ClickStateEnum.REST;
                        UserMessage.ClearStatus();
                        break;
                }
            }

            public override void HandleMouseMove(MouseEventArgs eventArgs, UIElement plotArea)
            {
                if (ClickState == ClickStateEnum.ROTATING)
                {
                    using (new BatchRedrawOperation(SelectAction.PellBitmap))
                    {
                        Point currentPoint = eventArgs.GetPosition(plotArea);
                        Point V1 = SecondPoint.Subtract(BasePoint);
                        Point V2 = currentPoint.Subtract(BasePoint);
                        double angle = Math.Atan2(V1.CrossProduct(V2), V1.DotProduct(V2));
                        CurrentTransform.Angle = angle;
                    }
                }
            }

            public override void Cancel()
            {
                using (new BatchRedrawOperation(SelectAction.PellBitmap))
                {
                    SelectAction.Selection.Each(g => g.CurrentTransforms.Remove(CurrentTransform));
                }
            }
        }

        public class MirrorSubAction : SubAction
        {
            public MirrorSubAction(SelectAction selectAction) : base(selectAction) { }

            public override void Initialize()
            {
                UserMessage.Status("Selecione a reta espelho desejada.");
            }

            private bool IsViableLine(GraphicObject lineWannabe)
            {
                return lineWannabe != null &&
                    lineWannabe is Model.Line &&
                    !lineWannabe.Selected;
            }

            private ObservableCollection<GraphicObject> RecreateSelection()
            {
                List<GraphicObject> l = new List<GraphicObject>(SelectAction.Selection);
                l.Sort(SelectAction.PellBitmap.CreateComparerByDrawingOrder());
                return new ObservableCollection<GraphicObject>(l);
            }

            public override void HandleClick(MouseButtonEventArgs eventArgs, UIElement plotArea)
            {
                GraphicObject lineWannabe = SelectAction.GetGraphicObjectAtCoordinates(eventArgs.GetPosition(plotArea));
                if (IsViableLine(lineWannabe))
                {
                    SelectAction.PellBitmap.RemoveObject(lineWannabe);
                    SelectAction.PellBitmap.AddObject(new ReflectionGroup(null, lineWannabe as Model.Line)
                    {
                        MirroredObjects = RecreateSelection()
                    });
                    SelectAction.SwitchToSubAction(new SelectSubAction(SelectAction));
                    UserMessage.Status("Objetos espelhados.");
                }
            }

            public override void HandleMouseMove(MouseEventArgs eventArgs, UIElement plotArea)
            {
                if (IsViableLine(SelectAction.GetGraphicObjectAtCoordinates(eventArgs.GetPosition(plotArea))))
                {
                    ((FrameworkElement)plotArea).Cursor = Cursors.Hand;
                }
                else
                {
                    ((FrameworkElement)plotArea).Cursor = Cursors.Arrow;
                }
            }

            public override void Cancel()
            {
                //throw new NotImplementedException();
            }
        }

        public class StaticMirrorSubAction : SubAction
        {
            public StaticMirrorSubAction(SelectAction selectAction) : base(selectAction) { }

            public override void Initialize()
            {
                UserMessage.Status("Selecione a reta espelho desejada.");
            }

            private bool IsViableLine(GraphicObject lineWannabe)
            {
                return lineWannabe != null &&
                    lineWannabe is Model.Line &&
                    !lineWannabe.Selected;
            }

            private ObservableCollection<GraphicObject> RecreateSelection()
            {
                List<GraphicObject> l = new List<GraphicObject>(SelectAction.Selection);
                l.Sort(SelectAction.PellBitmap.CreateComparerByDrawingOrder());
                return new ObservableCollection<GraphicObject>(l);
            }

            public override void HandleClick(MouseButtonEventArgs eventArgs, UIElement plotArea)
            {
                GraphicObject lineWannabe = SelectAction.GetGraphicObjectAtCoordinates(eventArgs.GetPosition(plotArea));
                if (IsViableLine(lineWannabe))
                {
                    Model.Line line = lineWannabe as Model.Line;
                    Point A = new Point(line.X1, line.Y1);
                    Point B = new Point(line.X2, line.Y2);
                    ITransform transform = new MirrorTransform(A, B.Subtract(A).Normalized());
                    ObservableCollection<GraphicObject> l = RecreateSelection();
                    foreach (GraphicObject g in l)
                    {
                        GraphicObject newG = g.CreateCopy();
                        newG.ConsolidateTransform(transform);
                        SelectAction.PellBitmap.AddObject(newG);
                    }
                    SelectAction.SwitchToSubAction(new SelectSubAction(SelectAction));
                    UserMessage.Status("Objetos espelhados.");
                }
            }

            public override void HandleMouseMove(MouseEventArgs eventArgs, UIElement plotArea)
            {
                if (IsViableLine(SelectAction.GetGraphicObjectAtCoordinates(eventArgs.GetPosition(plotArea))))
                {
                    ((FrameworkElement)plotArea).Cursor = Cursors.Hand;
                }
                else
                {
                    ((FrameworkElement)plotArea).Cursor = Cursors.Arrow;
                }
            }

            public override void Cancel()
            {
                //throw new NotImplementedException();
            }
        }

        public class NullSubAction : SubAction
        {
            public NullSubAction(SelectAction selectAction)
                : base(selectAction)
            { }

            public override void Initialize()
            { }

            public override void HandleClick(MouseButtonEventArgs eventArgs, UIElement plotArea)
            { }

            public override void HandleMouseMove(MouseEventArgs eventArgs, UIElement plotArea)
            { }

            public override void Cancel()
            { }
        }

        private readonly ObservableCollection<GraphicObject> selection = new ObservableCollection<GraphicObject>();
        private readonly SelectOptions pallete;

        public SelectAction(PellBitmap b) : base(b) 
        {
            this.pallete = new SelectOptions(this);
            this.selection.CollectionChanged += SelectionCollectionChanged;
            this.CurrentSubAction = new SelectSubAction(this);
        }

        private SubAction currentSubAction;
        private SubAction CurrentSubAction
        {
            get { return this.currentSubAction; }
            set { this.currentSubAction = value; }
        }

        private void ClearSelection()
        {
            while (Selection.Any())
            {
                selection.RemoveAt(0);
            }
        }

        private void SelectionCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                e.NewItems.Cast<GraphicObject>().Each(item => item.Selected = true);
            }
            else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
            {
                e.OldItems.Cast<GraphicObject>().Each(item => item.Selected = false);
            }
            else
            {
                throw new NotSupportedException();
            }
        }

        public override void HandleClick(MouseButtonEventArgs eventArgs, UIElement plotArea)
        {
            CurrentSubAction.HandleClick(eventArgs, plotArea);
        }

        public override void HandleMouseMove(MouseEventArgs eventArgs, UIElement plotArea)
        {
            CurrentSubAction.HandleMouseMove(eventArgs, plotArea);
        }

        public override void Cancel()
        {
            this.ClearSelection();
        }

        public override UIElement Pallete
        {
            get { return this.pallete; }
        }

        public ObservableCollection<GraphicObject> Selection
        {
            get { return this.selection; }
        }

        private void SwitchToSubAction(SubAction subAction)
        {
            CurrentSubAction.Cancel();
            CurrentSubAction = subAction;
            CurrentSubAction.Initialize();
        }

        internal void DoSelect()
        {
            SwitchToSubAction(new SelectSubAction(this));
        }

        internal void DoMove()
        {
            SwitchToSubAction(new MoveSubAction(this));
        }

        internal void DoRotate()
        {
            SwitchToSubAction(new RotateSubAction(this));
        }

        internal void DoGroupSelection()
        {
            SwitchToSubAction(new NullSubAction(this));
            var tempSelection = new ObservableCollection<GraphicObject>(Selection);
            ClearSelection();
            Group group = this.PellBitmap.GroupObjects(tempSelection);
            //this.selection.Add(group);
        }

        internal void DoUngroupSelection()
        {
            SwitchToSubAction(new NullSubAction(this));
            if (this.selection.All(o => o is Group))
            {
                IEnumerable<GraphicObject> newSelection = this.selection.SelectMany(g => this.PellBitmap.UngroupObjects((Group)g)).ToArray();
                ClearSelection();
                //newSelection.Each(item => this.selection.Add(item));
            }
        }

        internal void DoMirror()
        {
            SwitchToSubAction(new MirrorSubAction(this));
        }

        internal void DoStaticMirror()
        {
            SwitchToSubAction(new StaticMirrorSubAction(this));
        }

        internal void DoRemoveSelection()
        {
            List<GraphicObject> copy = new List<GraphicObject>(this.selection);
            this.ClearSelection();

            copy.Each(item => this.PellBitmap.RemoveObject(item));
        }

        public GraphicObject GetGraphicObjectAtCoordinates(Point baseLocation)
        {
            int baseX = (int)(baseLocation.X + 0.5);
            int baseY = (int)(baseLocation.Y + 0.5);

            for (int tolerance = 0; tolerance <= 2; ++tolerance)
            {
                for (int x = baseX - tolerance; x <= baseX + tolerance; ++x)
                {
                    for (int y = baseY - tolerance; y <= baseY + tolerance; ++y)
                    {
                        GraphicObject graphicObject = this.PellBitmap.GetObjectAtCoordinates(x, y);
                        if (graphicObject != null)
                        {
                            return graphicObject;
                        }
                    }
                }
            }

            return null;
        }

    }
}
